const { writeFileSync, readFileSync } = require("fs");
const Module = require("./module");
const MagicString = require("magic-string");
const { isAbsolute, resolve, dirname } = require("path");

module.exports = class Bundle {
    /**
     * @param {string} entry 要打包的入口文件路径
     */
    constructor({ entry = "" } = {}) {
        // 入口文件的绝对路径
        this.entryPath = entry.replace(/\.js$/, "") + ".js";
    }

    /**
     * bundle的打包函数
     * @param {string} outputFileName 打包后文件的路径
     */
    build(outputFileName) {
        // 1. 读取文件
        const entryModule = this.fetchModule(this.entryPath);

        // 2. 将读取的文件生成ast语法树，并解析ast语法树
        this.nodes = entryModule.expandAllStatements();

        // 3. 根据解析后的代码生成目标代码
        const code = this.generate();

        // 4. 根据目标代码，生成文件
        writeFileSync(outputFileName, code, "utf-8");
    }

    /**
     * 根据文件的绝对路径，读取模块
     * @param {string} importee 被调用者
     * @param {string} importer 调用者
     * @returns Module
     */
    fetchModule(importee, importer) {
        let route; // 入口文件的绝对路径

        // 1. 获取文件的绝对路径
        if(!importer) {
            // 如果没有模块导入，则表示为主模块
            route = importee;
        } else {
            // 如果是绝对路径，直接赋值
            if(isAbsolute(importee)) {
                route = importee;
            }
            // 如果是相对路径，将相对路径转化为绝对路径，再赋值
            else {
                route = resolve(dirname(importer), importee.replace(/\.js$/, "") + ".js");
            }
        }

        // 2. 开始解析模块
        if(route) {
            // 读取路径为 route 的文件代码
            const code = readFileSync(route, "utf-8").toString();

            // 解析当前代码模块，并返回
            const module = new Module({
                code,
                path: route,
                bundle: this
            })

            return module;
        } else {
            throw new Error("Bundle fetchModule 路径缺失!")
        }
    }

    /**
     * 把 this.nodes 生成代码
     */
    generate() {
        const magicString = new MagicString.Bundle();

        this.nodes.forEach(node => {
            const source = node._source.clone();

            // 去除掉 export const a = 1; 中的 export
            if(node.type === "ExportNamedDeclaration") {
                source.remove(node.start, node.declaration.start);
            }

            magicString.addSource({
                content: source,
                separator: "\n"
            })
        })

        return magicString.toString()
    }
}